﻿using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.SceneManagement;
using YooAsset;
using Object = UnityEngine.Object;

namespace Framework.Resource
{

    public partial class YooassetResourceHelper : MonoBehaviour, IResourceHelper
    {

        [SerializeField] public EPlayMode Mode = EPlayMode.HostPlayMode;

        public string packageName = "DefaultPackage";

        private ResourcePackage package;

        /// <summary>
        /// 记录对应handler
        /// </summary>
        private Dictionary<string, Queue<AssetHandle>> assetHandleNameMap;

        private Dictionary<int, string> assetInstanceIdNameMap;
        private Dictionary<string, SceneHandle> sceneHandles;

        private void Awake()
        {
            GetComponent<ResourceManager>().m_helper = this;
        }

        public void InitResource(InitResourceCallback callback)
        {
            assetHandleNameMap = new Dictionary<string, Queue<AssetHandle>>();
            assetInstanceIdNameMap = new Dictionary<int, string>();
            sceneHandles = new Dictionary<string, SceneHandle>();
            // 初始化资源系统
            YooAssets.Initialize();
            // 创建默认的资源包
            package = YooAssets.CreatePackage(packageName);
            // 设置该资源包为默认的资源包，可以使用YooAssets相关加载接口加载该资源包内容。
            YooAssets.SetDefaultPackage(package);
            switch (Mode)
            {
                case EPlayMode.EditorSimulateMode:
                    StartCoroutine(EditorModeInit(callback));
                    break;
                case EPlayMode.HostPlayMode:
                    StartCoroutine(HostPlayModeModeInit(callback));
                    break;
                case EPlayMode.OfflinePlayMode:
                    StartCoroutine(OfflineModeInit(callback));
                    break;
                case EPlayMode.WebPlayMode:
                    StartCoroutine(WebPlayModeInit(callback));
                    break;
            }
        }

        public void UpdateResourceVersion(UpdateResourceVersionCallback versionCallback)
        {
            if (Mode == EPlayMode.EditorSimulateMode || Mode == EPlayMode.OfflinePlayMode)
            {
                versionCallback.Invoke(true,false);
                return;
            }
            StartCoroutine(UpdatePackageVersion(versionCallback));
        }

        public void LoadAsset<T>(string assetName, LoadAssetCallback callback, object userData) where T : Object
        {
            Type assetType = typeof(T);
            LoadAsset(assetName, assetType, callback, userData);
        }

        public void LoadAsset(string assetName, Type assetType, LoadAssetCallback callback, object userData)
        {

            if (!package.CheckLocationValid(assetName))
            {
                callback.LoadAssetFailureCallback?.Invoke(assetName,"不存在",userData);
                Log.Error($"{assetName}不存在");
                return;
            }
            
            if (!assetHandleNameMap.ContainsKey(assetName))
            {
                assetHandleNameMap.Add(assetName, new Queue<AssetHandle>());
            }

            // package.GetAssetInfo(assetName).
            ///如果是同一个资产，那么就会找到对应的引用。返回同一个对象。但是handler却是新的
            /// handler仅仅只是句柄，对应引用的是同一个。因此可以维持一个dic<name,list<handler>>,卸载对象时，从list中取出。再去一个dic<instanceid,name>作为映射
            var handler = package.LoadAssetAsync(assetName, assetType);
            float time = Time.realtimeSinceStartup;
            assetHandleNameMap[assetName].Enqueue(handler);
            handler.Completed += (handler) =>
            {
                if (handler.AssetObject != null)
                {
                    callback.LoadAssetSuccessCallback.Invoke(assetName, handler.AssetObject, userData);
                    int instanceId = handler.AssetObject.GetInstanceID();
                    if (!assetInstanceIdNameMap.ContainsKey(instanceId))
                    {
                        assetInstanceIdNameMap.Add(instanceId, assetName);
                    }
                }
                else
                {
                    callback.LoadAssetFailureCallback.Invoke(assetName, handler.LastError, userData);
                }

            };
        }

        public void UnloadAsset(string assetName, UnloadAssetCallback callback)
        {
            if (assetHandleNameMap[assetName].Count > 0)
            {
                var handler = assetHandleNameMap[assetName].Dequeue();
                handler.Release();
            }
        }

        public void UnloadAsset(object asset, UnloadAssetCallback callback)
        {
            int instanceID = (asset as Object).GetInstanceID();
            if (assetInstanceIdNameMap.ContainsKey(instanceID))
            {
                string name = assetInstanceIdNameMap[instanceID];
                if (assetHandleNameMap[name].Count > 0)
                {
                    var handler = assetHandleNameMap[name].Dequeue();
                    handler.Release();
                }
            }
        }

        public void LoadScene(string sceneName, LoadSceneCallback callback, LoadSceneMode mode, bool suspendLoad)
        {
            var handler = package.LoadSceneAsync(sceneName, mode, suspendLoad);

            // float time = Time.realtimeSinceStartup;
            handler.Completed += (handler) =>
            {
                if (handler.SceneObject != null)
                {
                    callback.Invoke(sceneName, true);
                    sceneHandles.Add(sceneName, handler);
                }
                else
                {
                    callback.Invoke(sceneName, false);
                }

            };
        }

        public void UnLoadScene(string sceneName, UnloadSceneCallback callback)
        {
            SceneHandle handler = sceneHandles[sceneName];
            handler.UnloadAsync();
            sceneHandles.Remove(sceneName);
        }

        public void UnLoadUnusedAssets(UnLoadUnusedAssetsCallback callback)
        {
            package.UnloadUnusedAssets();
            callback.Invoke(true);
        }

        public T LoadAssetSync<T>(string assetName) where T : Object
        {
            Type assetType = typeof(T);
            return LoadAssetSync(assetName, assetType) as T;
        }

        public object LoadAssetSync(string assetName, Type assetType)
        {
            if (!package.CheckLocationValid(assetName))
            {
                return null;
            }
            if (!assetHandleNameMap.ContainsKey(assetName))
            {
                assetHandleNameMap.Add(assetName, new Queue<AssetHandle>());
            }
            
            // package.GetAssetInfo(assetName).
            ///如果是同一个资产，那么就会找到对应的引用。返回同一个对象。但是handler却是新的
            /// handler仅仅只是句柄，对应引用的是同一个。因此可以维持一个dic<name,list<handler>>,卸载对象时，从list中取出。再去一个dic<instanceid,name>作为映射
            var handler = package.LoadAssetSync(assetName, assetType);
            // float time = Time.realtimeSinceStartup;
            assetHandleNameMap[assetName].Enqueue(handler);
            
            
            object asset=handler.AssetObject;
            if (asset != null)
            {
                int instanceId = handler.AssetObject.GetInstanceID();
                if (!assetInstanceIdNameMap.ContainsKey(instanceId))
                {
                    assetInstanceIdNameMap.Add(instanceId, assetName);
                }
            }
            return asset;
                // return InternalLoadAssetSync(assetName, assetType);
        }

        async UniTask<object> InternalLoadAssetSync(string assetName, Type assetType)
        {
            if (!package.CheckLocationValid(assetName))
            {
                return null;
            }
            if (!assetHandleNameMap.ContainsKey(assetName))
            {
                assetHandleNameMap.Add(assetName, new Queue<AssetHandle>());
            }

            // package.GetAssetInfo(assetName).
            ///如果是同一个资产，那么就会找到对应的引用。返回同一个对象。但是handler却是新的
            /// handler仅仅只是句柄，对应引用的是同一个。因此可以维持一个dic<name,list<handler>>,卸载对象时，从list中取出。再去一个dic<instanceid,name>作为映射
            var handle = package.LoadAssetAsync(assetName, assetType);
            // float time = Time.realtimeSinceStartup;
            assetHandleNameMap[assetName].Enqueue(handle);

            // await handle.ToUniTask();
            await handle.ToUniTask();
            
            object asset=handle.AssetObject;
            if (asset != null)
            {
                int instanceId = handle.AssetObject.GetInstanceID();
                if (!assetInstanceIdNameMap.ContainsKey(instanceId))
                {
                    assetInstanceIdNameMap.Add(instanceId, assetName);
                }
            }
            return asset;
        }

        public void DownRemoteResource(DownloadCallback downloadCallback)
        {
            StartCoroutine(UpdatePackageManifest(downloadCallback));
        }
    }

}

